package com.foreveross.crawl.adapter.sub.impl20140402.v3.airfrance;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.foreveross.crawl.adapter.PlaneInfoEntityBuilder;
import com.foreveross.crawl.adapter.sub.impl20140402.v3.AirfranceAdapter;
import com.foreveross.crawl.domain.airfreight.CabinEntity;
import com.foreveross.crawl.domain.airfreight.doub.CabinRelationEntity;
import com.foreveross.crawl.domain.airfreight.doub.DoublePlaneInfoEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnCabinEntity;
import com.foreveross.crawl.domain.airfreight.doub.ReturnDoublePlaneInfoEntity;
import com.foreveross.crawl.exception.FlightInfoNotFoundException;
import com.foreveross.crawl.util.DateUtil;
import com.foreveross.taskservice.common.bean.TaskModel;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

/**
 * 法国航空往返，现只抓取直飞数据。后续如果要抓取全部，请在代码大致161行处去掉限制
 * 
 * @author fb
 */
@SuppressWarnings("deprecation")
public class AirfranceRound {

	private final static Logger logger = LoggerFactory.getLogger(AirfranceRound.class);

	private final String enterUrl = "http://www.airfrance.com.cn/cgi-bin/AF/CN/zh/local/process/standardbooking/ValidateSearchAction.do";
	
	// 出错后重复次数
	private final static int REPEATE_TIME = 3;
	
	private final static int PAGE_MAX_WAIT = 60000;
	
	private final static boolean isFetchTaxation = true;

	private AirfranceAdapter adapter;

	private TaskModel taskQueue;
	
	private WebClient webClient;		
	
	private HtmlPage htmlPage;
	
	private List<DoublePlaneInfoEntity> planeInfos = new ArrayList<DoublePlaneInfoEntity>();
	
	public static enum CabinType {
		Y_MCHER("经济舱（最低票价）"), F_MCHER("头等舱"), C_MCHER("商务舱"), W_MCHER("尊尚经济舱 "), Y_FLEX("经济舱 (可免费更改)");
		
		private String lable;
		
		CabinType(String lable) {
			this.lable = lable;
		}
		
		public String getLable() {
			return this.lable;
		}
	}

	public AirfranceRound(AirfranceAdapter adapter) {
		this.adapter = adapter;
		this.taskQueue = adapter.getTaskQueue();
	}

	public Object fetch() throws Exception {
		// 模仿浏览器从入口处进入
		webClient = adapter.getWebClientByFF();
		logActionInfo("模拟浏览器正在进入首页!");
		webClient.getPage(adapter.getUrl());
		hasWebClientRedirectEnabled();

		for (CabinType cabin : CabinType.values()) {
			fetchData(cabin);
		}
		
		if (planeInfos.isEmpty()) {
			throw new FlightInfoNotFoundException("没有此航班的信息!");
		}
		
		PlaneInfoEntityBuilder.buildLimitPrice(planeInfos);
		
		return planeInfos;
	}

	public List<DoublePlaneInfoEntity> flightInfoCrawl(String content) {

		return null;
	}

	private void fetchData(CabinType cabin) {
		String flightListHtml = null;
		ScriptResult sr = null;
		logActionInfo(String.format("正在进入[%s]的第二日历选择页面!", cabin.getLable()));
		String page = errorRepeatExecute(getEnterParamsUrl(cabin.name()));
		
		// 获取到日历选择页面后进行页面校验
		if (standardCalendarPageVerify(page))  {
			logActionInfo(String.format("程序正在[%s]日历选择页面模拟页面点击事件准备进入航班列表页!", cabin.getLable()));
			// 模拟点击下一部
			sr = htmlPage.executeJavaScript("javascript:validate();");
			// 暂时等待，不然获取不到数据
			webClient.waitForBackgroundJavaScriptStartingBefore(PAGE_MAX_WAIT);
			htmlPage = (HtmlPage) sr.getNewPage();
			flightListHtml = htmlPage.asXml();
			
			// 列表数据验证
			if (!flightListPageVerify(flightListHtml)) {
				logActionInfo(String.format("程序[%s]获取航班列表页面内容不正确!", cabin.getLable()));
				return;
			}
		
		// 如果校验不通过，再看判断看程序是否自动跳到了第三列表页
		} else {
			flightListHtml = page;
			
			if (!flightListPageVerify(flightListHtml)) {
				return;
			}
			
			logActionInfo(String.format("程序自动己从[%s]日历选择页面跳到航班列表页面!", cabin.getLable()));
		}
		
		logActionInfo(String.format("程序准备解析[%s]获取的航班信息进行数据封装!", cabin.getLable()));
		// 列表解析与数据封装
		flightInfoAssembly(flightListHtml, cabin);
	}
	
	public void flightInfoAssembly(String flightListPage, CabinType cabin) {
		Document doc = Jsoup.parse(flightListPage);
		HtmlForm form = htmlPage.getFormByName("standardUpsellForm");
		List<List<Map<String, String>>> outboundsData = new ArrayList<List<Map<String, String>>>();
		List<List<Map<String, String>>> inboundsData = new ArrayList<List<Map<String, String>>>();
		String detail = "";
		Document detailDoc = null;
		DoublePlaneInfoEntity dpie = null;
		ReturnDoublePlaneInfoEntity rdpe = null;
		Set<CabinEntity> cabins = null;
		Set<ReturnCabinEntity> returnCabins = null;
		
		// 数据解析封装
		flightInfoAnalysis(outboundsData, inboundsData, doc, cabin);
		
		logger.info("[{}]解析完成，正在准备数据组装!", cabin.getLable());
		
		// 数据组装
		for (List<Map<String, String>> outbound : outboundsData) {
			// 如果运营公司只有一家（中转同一家运营公司也会代存在多条数据在list中）则是直飞
			// 现只有直飞才进行数据组装, 如果到时要中转的只需这里代码移除即可
			if (outbound.size() > 1) {
				continue;
			}
			
			dpie = AirfranceUtils.flightBaseInfoAssembly(outbound, taskQueue, cabin.getLable());
			cabins = AirfranceUtils.cabinInfoAssembly(outbound, dpie.getCabins(), CabinEntity.class, cabin.getLable());
			planeInfos.add(dpie);
			
			for (List<Map<String, String>>  inbound : inboundsData) {
				rdpe = AirfranceUtils.flightReturnBaseInfoAssembly(inbound, taskQueue, cabin.getLable());
				returnCabins = AirfranceUtils.cabinInfoAssembly(outbound, rdpe.getReturnCabins(), ReturnCabinEntity.class, cabin.getLable());
				dpie.getReturnPlaneInfos().add(rdpe);
				
				
				if (!isFetchTaxation) {
					analyzeCabinRelation(Jsoup.parse(detail), dpie, cabins, returnCabins);
					continue;
				}
				
				logger.debug("正在获取仓位为[{}],去程id为[{}],回程id为[{}]的详细信息!", new Object[]{cabin.getLable(), outbound.get(0).get("id"), inbound.get(0).get("id")});
				detail = errorRepeatExecute(getFetchTaxationParams(form, outbound.get(0).get("id").toString(), inbound.get(0).get("id").toString()));
				
				if (StringUtils.isBlank(detail)) {
					logger.error("正在获取仓位为[{}],去程id为[{}],回程id为[{}]的详细信息失败!", new Object[]{cabin.getLable(), outbound, inbound});
					continue;
				}
				
				detailDoc = Jsoup.parse(detail);
				// 仓位关系组装
				analyzeCabinRelation(detailDoc, dpie, cabins, returnCabins);
			}
		}
	}
	
	// 航班列表页数据解析封装到map中
	private void flightInfoAnalysis (List<List<Map<String, String>>> outboundsData, List<List<Map<String, String>>> inboundsData, Document doc, CabinType cabin) {
		Elements outbound = doc.select("div#idUpsell0 table.upsell div[id^=Iti0Rec]");
		Elements inbound = doc.select("div#idUpsell1 table.upsell div[id^=Iti1Rec]");
		List<Map<String, String>> outbounds = null;
		List<Map<String, String>> inbounds = null;
		Map<String, String> outboundData = null;
		Map<String, String> inboundData = null;
		Elements flightAfterTd = null;
		String id = null;
		int index = 0;
		
		for (Element element : outbound) {
			outbounds = new ArrayList<Map<String, String>>();
			index = 0;
			id = element.attr("id");
			id = id.substring(id.length() - 1);

			for (Element companyTr : element.select("table.upsellRow tr[class!=lowSeatAvailable]")) {
				index++;
				outboundData = new HashMap<String, String>();
				outboundData.put("id", id);
				outboundData.put("priceOne", companyTr.select("#idPrice0Rec" + id + "FFFFLHAFKLY" + index).text());
				outboundData.put("priceTwo", companyTr.select("#idPrice0Rec" + id + "FFFFLHAFW" + index).text());
				flightAfterTd = companyTr.select("#idUpsellIti0_Rec" + id + "_Flight_" + index + " ~ td");
				outboundData.put("flightNo", companyTr.select("#idUpsellIti0_Rec" + id + "_Flight_" + index).text());
				outboundData.put("duration", flightAfterTd.get(4).text());
				outboundData.put("company", flightAfterTd.get(5).text());
				outboundData.put("fromDate", flightAfterTd.get(0).text());
				outboundData.put("toDate", flightAfterTd.get(2).text());
				outboundData.put("fromAddress", flightAfterTd.get(1).text());
				outboundData.put("toAddress", flightAfterTd.get(3).text());
				outboundData.put("cabin", cabin.getLable());
				outbounds.add(outboundData);
			}
			
			outboundsData.add(outbounds);
			
			for (Map<String, String> out : outbounds) {
				for (Entry<String, String> entry : out.entrySet()){
					logger.debug(entry.getKey() + ": " + entry.getValue());
				}
				
			   logger.debug(" ---------------------------------- ");
			}
			
			logger.debug(" =============================================== ");
		}
		
		logger.debug("======================反程=====================");
		
		for (Element element : inbound) {
			inbounds = new ArrayList<Map<String, String>>();
			index = 0;
			id = element.attr("id");
			id = id.substring(id.length() - 1);

			for (Element companyTr : element.select("table.upsellRow tr[class!=lowSeatAvailable]")) {
				index++;
				inboundData = new HashMap<String, String>();
				inboundData.put("id", id);
				inboundData.put("priceOne", companyTr.select("#idPrice1Rec" + id + "FFFFLHAFKLY" + index).text());
				inboundData.put("priceTwo", companyTr.select("#idPrice1Rec" + id + "FFFFLHAFW" + index).text());
				flightAfterTd = companyTr.select("#idUpsellIti1_Rec" + id + "_Flight_" + index + " ~ td");
				inboundData.put("flightNo", companyTr.select("#idUpsellIti1_Rec" + id + "_Flight_" + index).text());
				inboundData.put("duration", flightAfterTd.get(4).text());
				inboundData.put("company", flightAfterTd.get(5).text());
				inboundData.put("fromDate", flightAfterTd.get(0).text());
				inboundData.put("toDate", flightAfterTd.get(2).text());
				inboundData.put("fromAddress", flightAfterTd.get(1).text());
				inboundData.put("toAddress", flightAfterTd.get(3).text());
				inboundData.put("cabin", cabin.getLable());
				inbounds.add(inboundData);
			}
			
			inboundsData.add(inbounds);
			
			for (Map<String, String> out : inbounds) {
				for (Entry<String, String> entry : out.entrySet()){
					logger.debug(entry.getKey() + ": " + entry.getValue());
				}
				
			   logger.debug(" ---------------------------------- ");
			}
			
			logger.debug(" =============================================== ");
		}
	}
	
	/**
	 * 获取详细页面
	 * 
	 * @param form
	 * @param outbound
	 * @param inbound
	 */
	private void analyzeCabinRelation(Document detailDoc, DoublePlaneInfoEntity dpie, Set<CabinEntity> cabins, Set<ReturnCabinEntity> returnCabins) {
		String taxation = null;
		String bookTicketsPrice = null;
		String nakedPrce = null;
		String totalPrice =  null;
		
		// 如果抓取税费
		if (isFetchTaxation) {
			nakedPrce = detailDoc.select("#horsTaxe0").text();
			taxation = detailDoc.select("#taxe0").text();
			bookTicketsPrice = detailDoc.select("#fees0").text();
			totalPrice =  detailDoc.select("#total0").text();
			nakedPrce = StringUtils.isBlank(nakedPrce) ? "0" : nakedPrce.replaceAll("CNY|\\s| *", "");
			taxation = StringUtils.isBlank(taxation) ? "0" : taxation.replaceAll("\\+|CNY|\\s| *", "");
			bookTicketsPrice = StringUtils.isBlank(bookTicketsPrice) ? "0" : bookTicketsPrice.replaceAll("\\+|CNY|\\s| *", "");
			totalPrice = StringUtils.isBlank(totalPrice) ? "0" : totalPrice.replaceAll("=|CNY|\\s| *", "");
		}
		
		// 设置仓位对应关系
		for (CabinEntity cabin : cabins) {
			for (ReturnCabinEntity rcabin : returnCabins) {
				CabinRelationEntity cre = new CabinRelationEntity();
				cre.setCabinId(cabin.getId());
				cre.setReturnCabinId(rcabin.getId());
				cre.setFullPrice(isFetchTaxation ? Double.parseDouble(nakedPrce) : (cabin.getPrice() + rcabin.getPrice()) );
				cre.setTaxesPrice(isFetchTaxation ? Double.parseDouble(taxation) : 0);
				cre.setTotalFullPrice(isFetchTaxation ? Double.parseDouble(totalPrice) : (cre.getFullPrice() + cre.getTaxesPrice()));
				dpie.getCabinRelations().add(cre);
			}
		}
	}
	
	private String errorRepeatExecute(String parmsUrl) {
		String content = null;
		int timeError = 0;
		boolean error = false;

		do {
			try {
				htmlPage = webClient.getPage(parmsUrl);
				//阻塞线程js执行完毕或者达到指定时间
				webClient.waitForBackgroundJavaScriptStartingBefore(PAGE_MAX_WAIT);
				content = htmlPage.asXml();
			} catch (Exception e) {
				error = true;
				timeError++;

				logger.error("获取法国航空信息第{}次出错:{}", timeError, e.getMessage());

				if (timeError >= REPEATE_TIME) {
					logger.error("深度获取法国航空信息异常，中止尝试", e);
				} else {
					adapter.switchProxyipByWebClient();
				}
			}
		} while (error || StringUtils.isBlank(content));

		return content;
	}

	private void hasWebClientRedirectEnabled(){
		webClient.getOptions().setRedirectEnabled(true);
		webClient.getOptions().setJavaScriptEnabled(true);
		webClient.getOptions().setActiveXNative(false);
		webClient.getOptions().setCssEnabled(false);
		webClient.getOptions().setThrowExceptionOnScriptError(false);
		webClient.setAjaxController(new NicelyResynchronizingAjaxController());
	}
	
	
	// 第二页面，日历选择页面
	private boolean standardCalendarPageVerify(String page) {
		boolean result  = false;
		Document doc = null;
		
		if (StringUtils.isNotBlank(page)) {
			doc = Jsoup.parse(page);
			Elements form  = doc.select("div.blocMain > form[name=standardCalendarForm]");
			result = form.isEmpty() ? result : true;
		}
		
		return result;
	}
	
	// 航班列表页面验证
	private boolean flightListPageVerify(String flightPage) {
		boolean result  = false;
		Document doc = null;
		
		if (StringUtils.isNotBlank(flightPage)) {
			 doc = Jsoup.parse(flightPage);
			Elements select  = doc.select("select[id^=idSortTypeSelect]");
			result = select.isEmpty() ? result : true;
		}
		
		return result;
	}

	private String getEnterParamsUrl(String cablin) {
		StringBuilder url = new StringBuilder(enterUrl);
		String flightDate = null;
		String returnGrabDate = null;
		
		try {
			flightDate = DateUtil.String2String(taskQueue.getFlightDate(), "yyyy-MM-dd", "yyyyMM");
			returnGrabDate = DateUtil.String2String(taskQueue.getReturnGrabDate(), "yyyy-MM-dd", "yyyyMM");
		} catch (ParseException e) {
			AirfranceAdapter.logger.error("参数时间格式化处理出错 : ", e);
		} 
				
		url.append("?")
		.append("arrival=").append(taskQueue.getToCity())
		.append("&arrival=").append(taskQueue.getFromCity())
		.append("&cabin=").append(cablin.split("_")[0])
		.append("&dayDate=").append(taskQueue.getFlightDate().substring(taskQueue.getFlightDate().lastIndexOf("-") + 1))
		.append("&dayDate=").append(taskQueue.getReturnGrabDate().substring(taskQueue.getReturnGrabDate().lastIndexOf("-") + 1))
		.append("&departure=").append(taskQueue.getFromCity())
		.append("&departure=").append(taskQueue.getToCity())
		.append("&familyTrip=").append("NON")
		.append("&haul=").append("LH")
		.append("&idArrivalTrip1Lib=").append(taskQueue.getToCity() + " (" + taskQueue.getToCity() + ")")
		.append("&idDepartureTrip1Lib=").append(taskQueue.getFromCity() +  " (" + taskQueue.getFromCity() + ")")
		.append("&isUM=").append("")
		.append("&jourAllerFin=").append("06")
		.append("&jourAllerOrigine=").append("11")
		.append("&moisAllerFin=").append("201509")
		.append("&moisAllerOrigine=").append("201409")
		.append("&nbEnfants=").append("")
		.append("&nbPassenger=").append("1")
		.append("&paxTypoList=").append("ADT")
		.append("&paxTypoList=").append("ADT")
		.append("&paxTypoList=").append("ADT")
		.append("&paxTypoList=").append("ADT")
		.append("&plusOptions=").append("")
		.append("&selectCabin=").append("Y_MCHER")
		.append("&selectCabin=").append("Y_MCHER")
		.append("&selectCabin=").append(cablin)
		.append("&selectCabin=").append("Y_MCHER")
		.append("&selectPreviousSearch=").append(taskQueue.getFromCity() + "-" + taskQueue.getToCity() + "_2014811")
		.append("&subCabin=").append(cablin.split("_")[1])
		.append("&typeTrip=").append("2")
		.append("&yearMonthDate=").append(flightDate)
		.append("&yearMonthDate=").append(returnGrabDate);
		
		return url.toString();
	}
	
	private String getFetchTaxationParams(HtmlForm form, String outbound, String inbound){
		StringBuilder url = new StringBuilder("http://www.airfrance.com.cn/cgi-bin/AF/CN/zh/local/process/standardbooking/FareAction.do?");
		String indexItinerary = form.getInputByName("indexItinerary").getValueAttribute();
		url.append("&indexItinerary=").append(indexItinerary);
		List<HtmlInput>  prices = form.getInputsByName("price");
		List<HtmlInput>  priceMultiPaxs = form.getInputsByName("priceMultiPax");
		List<HtmlInput>  selectedFFs = form.getInputsByName("selectedFF");
		List<HtmlInput>  recommendations = form.getInputsByName("recommendation");
		
		for (HtmlInput price : prices) {
			url.append("&price=").append(price.getValueAttribute());
		}
		
		for (HtmlInput priceMultiPax : priceMultiPaxs) {
			url.append("&priceMultiPax=").append(priceMultiPax.getValueAttribute());
		}
		
		for (HtmlInput selectedFF : selectedFFs) {
			url.append("&selectedFF=").append(selectedFF.getValueAttribute());
		}
		
		url.append("&radioIti0=").append(outbound.trim())
		   .append("&radioIti1=").append(inbound.trim());
		
		for (HtmlInput recommendation : recommendations) {
			url.append("&recommendation=").append(recommendation.getValueAttribute());
		}
		   // 排序类型写死
		url.append("&sortType=DEPARTURE")
		   .append("&sortType=DEPARTURE")
		   .append("&sortType0=DEPARTURE")
		   .append("&sortType1=DEPARTURE");
		
		return url.toString();
	}
	
	/**
	 * 记录活动日志，主要是记录到task中
	 * 
	 * @param info
	 */
	private void logActionInfo(String info) {
		logger.info(info);
		//adapter.logInfo(info);
	}

}
